/*
 *
 * $Id: ChannelMessenger.java,v 1.1 2005/05/03 06:38:20 hamada Exp $
 *
 * Copyright (c) 2004 Sun Microsystems, Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *       Sun Microsystems, Inc. for Project JXTA."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA"
 *    must not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact Project JXTA at http://www.jxta.org.
 *
 * 5. Products derived from this software may not be called "JXTA",
 *    nor may "JXTA" appear in their name, without prior written
 *    permission of Sun.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL SUN MICROSYSTEMS OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of Project JXTA.  For more
 * information on Project JXTA, please see
 * <http://www.jxta.org/>.
 *
 * This license is based on the BSD license adopted by the Apache Foundation.
 */

package net.jxta.endpoint;

import java.io.IOException;
import java.io.InterruptedIOException;

import net.jxta.peergroup.PeerGroupID;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

/**
 * A Channel Messenger provides an exclusive interface to the shared messenger. <p/>
 *
 * What is typically exclusive is the message queue, addressing parameters that are not usefully shared (serviceName,
 * serviceParam), and if needed cross-group address rewritting parameters.<p/>
 *
 * This class is provided as a base for implementing such channel messengers, which are typically what
 * Messenger.getChannelMessenger() needs to return. <p/>
 *
 * @see net.jxta.endpoint.EndpointService
 * @see net.jxta.endpoint.EndpointAddress
 * @see net.jxta.endpoint.Message
 */
public abstract class ChannelMessenger extends AbstractMessenger implements Messenger {

    /**
     * insertedServicePrefix This is how all valid inserted services start. This lets us recognize if a message already has an
     * inserted service. Then we must not add another one. Only the top-most one counts.  Since insertion is only done here, the
     * constant is defined here. Even if historically it was done within the endpoint implementation, it has become a protocol now.
     */
    public static final String InsertedServicePrefix = "EndpointService:";

    private String insertedService;

    /**
     * The worker that implements sendMessage-with-listener for this channel.  If there's none, sendMessage-with-listener will
     * throw an exception. Channels returned by getMessenger methods all have one already. It is up to the invoker of
     * getChannelMessenger to supply one or not.
     */
    private ListenerAdaptor messageWatcher;

    protected String origService;

    protected String origServiceParam;

    /**
     * Figure out what the service string will be after mangling (if required) and applying relevant defaults.
     * @param service The service name in the unmangled address.
     * @return String The service name in the mangled address.
     */
    protected String effectiveService(String service) {

        // No redirection required. Just apply the default service.
        if (insertedService == null) {
            return (service == null) ? origService : service;
        }

        // Check if redirection is applicable.
        return ((service != null) && service.startsWith(InsertedServicePrefix)) ? service : insertedService;
    }

    /**
     * Figure out what the param string will be after mangling (if required) and applying relevant defaults.
     * @param service The service name in the unmangled address.
     * @param serviceParam The service parameter in the unmangled address.
     * @return String The service parameter in the mangled address.
     */
    protected String effectiveParam(String service, String serviceParam) {

        // No redirection required. Or not applicable. Just apply the default param.
        if ((insertedService == null) || ((service != null) && service.startsWith(InsertedServicePrefix))) {
            return (serviceParam == null) ? origServiceParam : serviceParam;
        }

        // Apply redirection. We need the effective service, now.
        if (service == null) {
            service = origService;
        }

        if (serviceParam == null) {
            serviceParam = origServiceParam;
        }

        return ((null != service) && (null != serviceParam)) ? (service + "/" + serviceParam) : service;
    }

    /**
     * Give this channel the watcher that it must use whenever sendMessage(...,listener) is used. If not set,
     * sendMessage(..., listener) will throw.
     * @deprecated This method is in support of deprecated methods.
     */
    public void setMessageWatcher(ListenerAdaptor messageWatcher) {
        this.messageWatcher = messageWatcher;
    }

    /**
     * Create a new ChannelMessenger
     * 
     * @param baseAddress The network address messages go to; regardless of service, param, or group.
     * @param groupRedirection Group to which the messages must be redirected. This is used to implement the automatic group
     * seggregation which has become a de-facto standard. If not null, the unique portion of the specified groupID is
     * prepended with {@link #InsertedServicePrefix} and inserted in every message's destination address in place of the
     * the original service name, which gets shifted into the begining of the service parameter. The opposite is done
     * on arrival to restore the original destination address before the message is delivered to the listener in the
     * the specified group. Messages that already bear a group redirection are not affected.
     * @param origService The default destination service for messages sent without specifying a different service.
     * @param origServiceParam The default destination service parameter for messages sent without specifying a different service
     * parameter.
     */
    public ChannelMessenger(EndpointAddress baseAddress, PeerGroupID groupRedirection,
                            String origService, String origServiceParam) {


        // FIXME: The inserted service business is really messy. Group seggregation does not have to be the endpoint service's
        // business. It should be specified by the app as part of the destination address. What we're doing here
        // is simply enforcing what could just be a convention.

        super(baseAddress);
        if (groupRedirection == null) {
            insertedService = null;
        } else {
            insertedService = InsertedServicePrefix + groupRedirection.getUniqueValue().toString();
        }
        this.origService = origService;
        this.origServiceParam = origServiceParam;

        // Now that we're initialized enough, grab the destination addr from our superclass and fix it.  We add the service and
        // param; NOT the group mangling: getDestinationAddress[Object]() should always return what was given at construction.
        EndpointAddress dest = getDestinationAddressObject();
        dest.setServiceName(origService);
        dest.setServiceParameter(origServiceParam);
    }

    /**
     * {@inheritDoc}
     *
     * <p/> By default a channel refuses to make a channel.
     *
     */
    public Messenger getChannelMessenger(PeerGroupID redirection, String service, String serviceParam) {
        return null;
    }

    /**
     * {@inheritDoc}
     *
     * @deprecated Implements deprecated method.
     */
    public void sendMessage(Message msg, String service, String serviceParam, OutgoingMessageEventListener listener) {
        if (messageWatcher == null) {
            throw new UnsupportedOperationException("This channel was not configured to emulate this legacy method.");
        }

        // Cleanup the message from any existing result prop since we're going to use select.
        msg.setMessageProperty(Messenger.class, null);

        // Tell the watcher to select that message.
        messageWatcher.watchMessage(listener, msg);

        sendMessageN(msg, service, serviceParam);
    }
}
